/*_############################################################################
  _##
  _##  SNMP4J-Agent - NMSDefaultMOTable.java
  _##
  _##  Copyright (C) 2005-2008  Frank Fock (SNMP4J.org)
  _##
  _##  Licensed under the Apache License, Version 2.0 (the "License");
  _##  you may not use this file except in compliance with the License.
  _##  You may obtain a copy of the License at
  _##
  _##      http://www.apache.org/licenses/LICENSE-2.0
  _##
  _##  Unless required by applicable law or agreed to in writing, software
  _##  distributed under the License is distributed on an "AS IS" BASIS,
  _##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  _##  See the License for the specific language governing permissions and
  _##  limitations under the License.
  _##
  _##########################################################################*/

package org.snmp4j.agent.mo;

import java.io.*;
import java.util.*;

import org.snmp4j.*;
import org.snmp4j.agent.*;
import org.snmp4j.agent.io.*;
import org.snmp4j.agent.request.*;
import org.snmp4j.agent.util.*;
import org.snmp4j.log.*;
import org.snmp4j.mp.*;
import org.snmp4j.smi.*;

import com.suretech.nms.subagent.AgentTableService;

/**
 * The <code>NMSDefaultMOTable</code> class is the default implementation of
 * the {@link MOTable} class. For most use cases, it is not necessary to
 * customize this class through deriving your own sub-class. Instead, using a
 * different {@link MOTableModel} as table data provider is sufficient.
 * <p>
 * The default table model can be used to hold the data of a SNMP conceptual
 * table as real tabular data. If you want to implement a virtual table, you
 * will have to directly implement the interfaces {@link MOTableModel} or
 * {@link MOMutableTableModel} to access the data based on the actual view.
 * 
 * @author Frank Fock
 * @version 1.2
 */
// refer to DefaultMOTable

@SuppressWarnings("unchecked")
public class NMSMOTable implements MOTable, MOScope, SerializableManagedObject {
 
	private static LogAdapter logger = LogFactory.getLogger(NMSMOTable.class);

	private OID oid;
	private MOTableIndex indexDef;
	private MOColumn[] columns;
	protected MOTableModel model;

	private boolean isVolatile;

	protected WeakHashMap newRows;
	protected WeakHashMap pendingChanges;

	protected transient Vector moChangeListeners;
	protected transient Vector moTableRowListeners;

	private transient WeakHashMap walkCache;
	
	private static AgentTableService agentTableService;

	private static Comparator columnComparator = new Comparator() {

		public int compare(Object o1, Object o2) {
			int id1 = (o1 instanceof MOColumn) ? ((MOColumn) o1).getColumnID()
					: ((Integer) o1).intValue();
			int id2 = (o2 instanceof MOColumn) ? ((MOColumn) o2).getColumnID()
					: ((Integer) o2).intValue();
			return id1 - id2;
		}
	};

	public NMSMOTable(OID oid, MOTableIndex indexDef, MOColumn[] columns) {
		this(oid, indexDef, columns, new NMSMOMutableTableModel(getAgentTableService()));
	}
	 
	public NMSMOTable(OID oid, MOTableIndex indexDef, MOColumn[] columns,
			MOTableModel model) {
		this.oid = oid;
		this.indexDef = indexDef;
		this.columns = columns;
		this.model = model;
		registerColumns();
	}

	private void registerColumns() {
		for (int i = 0; i < columns.length; i++) {
			columns[i].setTable(this);
		}
	}

	public MOTableCellInfo getCellInfo(OID oid) {
		return new CellInfo(oid);
	}

	public int getColumnIndex(int id) {
		int col = Arrays.binarySearch(columns, new Integer(id),
				columnComparator);
		return col;
	}

	public MOColumn getColumn(int index) {
		return columns[index];
	}

	public int getColumnCount() {
		return columns.length;
	}

	/**
	 * Creates a new row for this table with the supplied index and initial
	 * values. If the underlying table model is not a
	 * {@link MOMutableTableModel} instance or if one of the
	 * {@link MOTableRowListener} deny the row creation attempt then
	 * <code>null</code> will be returned.
	 * 
	 * @param index
	 *            the index OID of the new row.
	 * @param initialValues
	 *            the initial values that should be assigned to the new row. If
	 *            the array contains less values than this table has columns,
	 *            default values will be created for the missing columns.
	 * @return the created <code>MOTableRow</code> instance or
	 *         <code>null</code> if the row cannot be created.
	 */
	public MOTableRow createRow(OID index, Variable[] initialValues) {
		if (model instanceof MOMutableTableModel) {
			Variable[] values = initialValues;
			if (values.length < getColumnCount()) {
				values = getDefaultValues();
				System.arraycopy(initialValues, 0, values, 0,
						initialValues.length);
			}
			MOTableRow row = ((MOMutableTableModel) model).createRow(index,
					values);
			MOTableRowEvent rowEvent = new MOTableRowEvent(this, this, row,
					MOTableRowEvent.CREATE, true);
			fireRowChanged(rowEvent);
			if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
				return row;
			}
		}
		return null;
	}

	public MOTableRow createRow(OID index) {
		return createRow(index, getDefaultValues());
	}

	/**
	 * Adds the supplied row to the underlying table model and fires the
	 * appropriate {@link MOTableRowEvent}. Since this method is typically
	 * called during the commit phase of a SET request that creates a table, it
	 * should be avoided to return an error here. Instead, error checking should
	 * be placed in the {@link #prepare} method.
	 * 
	 * @param row
	 *            the <code>MOTableRow</code> to add.
	 * @return <code>true</code> if the row has been added or
	 *         <code>false</code> if it could not be added.
	 */
	public boolean addRow(MOTableRow row) {
		if (model instanceof MOMutableTableModel) {
			MOTableRowEvent rowEvent = new MOTableRowEvent(this, this, row,
					MOTableRowEvent.ADD, true);
			fireRowChanged(rowEvent);
			if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
				((MOMutableTableModel) model).addRow(row);
			}
			return true;
		}
		return false;
	}

	public MOTableRow removeRow(OID index) {
		if (model instanceof MOMutableTableModel) {
			MOTableRow row = model.getRow(index);
			if (row == null) {
				return null;
			}
			MOTableRowEvent rowEvent = new MOTableRowEvent(this, this, row,
					MOTableRowEvent.DELETE, true);
			fireRowChanged(rowEvent);
			if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
				return ((MOMutableTableModel) model).removeRow(index);
			}
		}
		return null;
	}

	/**
	 * Removes all rows from this table. Before a row is removed the
	 * corresponding DELETE event is fired and listeners may veto these events
	 * for all rows. Only if there is no veto, a row will be deleted. The number
	 * of deleted rows is then returned.
	 * 
	 * @return the number of removed rows or -1 if the table model does not
	 *         support row removal.
	 */
	public int removeAll() {
		int count = 0;
		if (model instanceof MOMutableTableModel) {
			while (model.getRowCount() > 0) {
				MOTableRow row = model.firstRow();
				if (row != null) {
					MOTableRowEvent rowEvent = new MOTableRowEvent(this, this,
							row, MOTableRowEvent.DELETE, true);
					fireRowChanged(rowEvent);
					if (rowEvent.getVetoStatus() == SnmpConstants.SNMP_ERROR_SUCCESS) {
						((MOMutableTableModel) model).removeRow(row.getIndex());
					}
					count++;
				}
			}
		} else {
			count = -1;
		}
		return count;
	}

	public void commit(SubRequest request) {
		System.err.println("commit.....");
		OID cellOID = request.getVariableBinding().getOid();
		MOTableCellInfo cell = getCellInfo(cellOID);
		MOMutableColumn col = (MOMutableColumn) getColumn(cell.getColumn());
		if (logger.isDebugEnabled()) {
			logger.debug("Committing sub-request (" + request.getVariableBinding() + ") for column: " + col);
		}
		//Committing sub-request (1.3.6.1.4.1.31305.206.2.1.55.2 = 222) for column: org.snmp4j.agent.mo.MOMutableColumn[columnID=55,syntax=2,default=222,mode=]
		// Make sure changes are atomic -> sync whole table model
		synchronized (model) {
			MOMutableTableRow row;
			if (hasNewRows(request.getRequest())) {
				row = (MOMutableTableRow) getNewRows(request.getRequest()).get(
						cell.getIndex());
				addRow(row);
			} else {
				row = (MOMutableTableRow) model.getRow(cell.getIndex());
			}
			Variable oldValue = null;
			if (moChangeListeners != null) {
				oldValue = row.getValue(cell.getColumn());
				MOChangeEvent changeEvent = new MOChangeEvent(this,new CellProxy(cell), cell.getCellOID(), oldValue,
						request.getVariableBinding().getVariable(), false);
				fireBeforeMOChange(changeEvent);
			}
			ChangeSet changeSet = getPendingChangeSet(request, cell.getIndex());
			// commit
			col.commit(request, row, changeSet, cell.getColumn());
			if (moChangeListeners != null) {
				MOChangeEvent changeEvent = new MOChangeEvent(this,
						new CellProxy(cell), cell.getCellOID(), oldValue,
						request.getVariableBinding().getVariable(), false);
				fireAfterMOChange(changeEvent);
			}
			if (isChangeSetComplete(request, cell.getIndex(), cell.getColumn())) {
				if (row instanceof MOMutableRow2PC) {
					((MOMutableRow2PC) row).commitRow(request, changeSet);
				}
				if (moTableRowListeners != null) {
					MOTableRowEvent rowEvent = new MOTableRowEvent(this, this,
							row, MOTableRowEvent.UPDATED);
					fireRowChanged(rowEvent);
				}
//				MOTableCellInfo indexCell = getCellInfo(cell.getIndex());
//				System.err.println("cell.getIndex()="+cell.getIndex().toString());
//				Variable var=row.getValue(indexCell.getColumn());
				
				agentTableService.setColumnValue(cell.getIndex(), cell.getColumn(), request.getVariableBinding().getVariable());
			}
		}
	}

	public final OID getIndexPart(OID anyOID) {
		int offset = oid.size() + 1;
		if ((anyOID.size() <= offset) || (!anyOID.startsWith(oid))) {
			return null;
		}
		return new OID(anyOID.getValue(), offset, anyOID.size() - offset);
	}

	public OID getCellOID(OID index, int col) {
		OID retval = new OID(oid);
		retval.append(columns[col].getColumnID());
		retval.append(index);
		return retval;
	}

	private MOTableCellInfo getNextCell(int col, OID indexLowerBound,
			boolean isLowerBoundIncluded) {
		for (int i = col; i < columns.length; i++) {
			Iterator it = model.tailIterator(indexLowerBound);
			if (!it.hasNext()) {
				if (indexLowerBound == null) {
					return null;
				}
				indexLowerBound = null;
				isLowerBoundIncluded = true;
				continue;
			} else {
				if ((indexLowerBound != null) && (!isLowerBoundIncluded)) {
					MOTableRow row = (MOTableRow) it.next();
					if (row.getIndex().compareTo(indexLowerBound) > 0) {
						// the specified index does not exists so we can use
						// this next one:
						return new CellInfo(row.getIndex(), i, columns[i]
								.getColumnID(), row);
					}
				}
				indexLowerBound = null;
				isLowerBoundIncluded = true;
				if (it.hasNext()) {
					MOTableRow row = (MOTableRow) it.next();
					if (row == null) {
						continue;
					}
					return new CellInfo(row.getIndex(), i, columns[i]
							.getColumnID(), row);
				}
			}
		}
		return null;
	}

	public OID find(MOScope range) {
		MOTableCellInfo cellInfo = findCell(range, null);
		if (cellInfo != null) {
			return cellInfo.getCellOID();
		}
		return null;
	}

	protected MOTableCellInfo findCell(MOScope range, SubRequest request) {
		synchronized (model) {
			update(range, request);
			// determine column
			if (model.getRowCount() == 0) {
				return null;
			}
			MOTableCellInfo cellInfo = getCellInfo(range.getLowerBound());
			int col = cellInfo.getColumn();
			boolean exactMatch = true;
			if (col < 0) {
				col = (-col) - 1;
				exactMatch = false;
			}
			if (col >= columns.length) {
				return null;
			}
			boolean lowerIncluded = (!exactMatch) || range.isLowerIncluded();
			RowCacheEntry rowEntry = null;
			if (request != null) {
				rowEntry = getWalkCacheEntry(request, cellInfo, lowerIncluded);
			}
			MOTableCellInfo next;
			if (rowEntry != null) {
				next = new CellInfo(rowEntry.row.getIndex(), col, cellInfo
						.getColumnID(), rowEntry.row);
			} else {
				next = getNextCell(col, cellInfo.getIndex(), lowerIncluded);
				if ((request != null) && (next != null)
						&& (next.getColumn() == col)) {
					addWalkCacheEntry(request, cellInfo.getIndex(),
							lowerIncluded, ((CellInfo) next).row);
				}
			}
			if (next != null) {
				OID cellOID = next.getCellOID();
				if (range.isCovered(new OIDScope(cellOID))) {
					return next;
				}
			}
		}
		return null;
	}

	private void addWalkCacheEntry(SubRequest request, OID lowerBound,
			boolean lowerIncluded, MOTableRow row) {
		if (walkCache == null) {
			walkCache = new WeakHashMap(4);
		}
		walkCache.put(request.getRequest(), new RowCacheEntry(row, lowerBound,
				lowerIncluded));
	}

	private RowCacheEntry getWalkCacheEntry(SubRequest request,
			MOTableCellInfo cellInfo, boolean lowerIncluded) {
		if (walkCache != null) {
			RowCacheEntry entry = (RowCacheEntry) walkCache.get(request
					.getRequest());
			if (entry == null) {
				return null;
			}
			if (((entry.searchLowerBound == null) && (cellInfo.getIndex() == null))
					|| ((entry.searchLowerBound != null)
							&& (entry.searchLowerBound.equals(cellInfo
									.getIndex())) && (lowerIncluded == entry.searchLowerBoundIncluded))) {
				return entry;
			}
		}
		return null;
	}

	public MOScope getScope() {
		return this;
	}

	public Variable getValue(OID cellOID) {
		MOTableCellInfo cell = getCellInfo(cellOID);
		if ((cell.getIndex() != null) && (cell.getColumn() >= 0)
				&& (cell.getColumn() < columns.length)) {
			return getValue(cell.getIndex(), cell.getColumn());
		}
		return null;
	}

	public Variable getValue(OID index, int col) {
		MOTableRow row = model.getRow(index);
		return getValue(row, col);
	}

	protected Variable getValue(MOTableRow row, int col) {
		System.err.println("getValue(MOTableRow row, int col).........");
		if ((row != null) && (col >= 0) && (col < row.size())) {
			return columns[col].getValue(row, col);
		}
		return null;
	}

	/**
	 * Update the content of this table that is covered by the supplied scope.
	 * <p>
	 * This method is part of the {@link UpdatableManagedObject} interface.
	 * Although the {@link NMSMOTable} does not implement that interface,
	 * subclasses of this class may do so easily by overriding this hook-up
	 * method.
	 * 
	 * @param updateScope
	 *            the scope to update. If <code>null</code> the whole managed
	 *            object is updated.
	 * @since 1.2
	 * @see #update(MOScope range, SubRequest request)
	 */
	public void update(MOScope updateScope) {
		// nothing to do by default -> overwrite to update an updatable table.
	}

	/**
	 * Update this table for the supplied search range and sub-request if it has
	 * not yet been updated for that request.
	 * <p>
	 * By default, the {@link #update(MOScope updateScope)} is being called on
	 * behalf of this method call (which itself does not nothing by default).
	 * You may choose either to implement the {@link UpdatableManagedObject}
	 * interface and implement its interface in a subclass. Then it is
	 * recommended to overwrite this method by an empty method. Otherwise, do
	 * not implement the {@link UpdatableManagedObject} interface.
	 * </p>
	 * 
	 * @param range
	 *            the search range.
	 * @param request
	 *            the sub-request triggered the update or <code>null</code> if
	 *            that request cannot be determined.
	 */
	protected void update(MOScope range, SubRequest request) {
		System.err.println("update.......");
		Object updateMarker = null;
		if (request != null) {
			updateMarker = request.getRequest().getProcessingUserObject(
					getOID());
		}
		if (updateMarker == null) {
			if (request != null) {
				request.getRequest().setProcessingUserObject(getOID(),
						new Object());
			}
			update(range);
		}
	}
	public void get(SubRequest request) {
		System.err.println("get...........");
		OID cellOID = request.getVariableBinding().getOid();
		MOTableCellInfo cell = getCellInfo(cellOID);
		if ((cell.getIndex() != null) && (cell.getColumn() >= 0)
				&& (cell.getColumn() < columns.length)) {
			// update the table part affected by this query
			update(request.getScope(), request);
			MOColumn col = getColumn(cell.getColumn());
			System.err.println("cell.getColumn()="+cell.getColumn());
			MOTableRow row = model.getRow(cell.getIndex());
			if (row == null) {
				request.getVariableBinding().setVariable(Null.noSuchInstance);
			} else if (col != null) {
				col.get(request, row, cell.getColumn());
			} else {
				request.getStatus().setErrorStatus(PDU.noAccess);
			}
		} else {
			if (cell.getColumn() >= 0) {
				request.getVariableBinding().setVariable(Null.noSuchInstance);
			} else {
				request.getVariableBinding().setVariable(Null.noSuchObject);
			}
		}
		request.completed();
	}

	public boolean next(SubRequest request) {
		System.err.println("next.......");
		DefaultMOScope scope = new DefaultMOScope(request.getScope());
		MOTableCellInfo nextCell;
		while ((nextCell = findCell(scope, request)) != null) {
			if (columns[nextCell.getColumn()].getAccess().isAccessibleForRead()) {
				Variable value;
				// Use row instance from cell info as shortcut if available
				if ((nextCell instanceof CellInfo)
						&& (((CellInfo) nextCell).getRow() != null)) {
					value = getValue(((CellInfo) nextCell).getRow(), nextCell.getColumn());
				} else {
					value = getValue(nextCell.getIndex(), nextCell.getColumn());
				}
				if (value == null) {
					scope.setLowerBound(nextCell.getCellOID());
					scope.setLowerIncluded(false);
				} else {
					request.getVariableBinding().setOid(nextCell.getCellOID());
					request.getVariableBinding().setVariable(value);
					request.completed();
					return true;
				}
			} else {
				if (nextCell.getColumn() + 1 < getColumnCount()) {
					OID nextColOID = new OID(getOID());
					nextColOID.append(columns[nextCell.getColumn() + 1]
							.getColumnID());
					scope.setLowerBound(nextColOID);
					scope.setLowerIncluded(false);
				} else {
					return false;
				}
			}
		}
		return false;
	}

	/**
	 * prepare
	 * 
	 * @param request
	 *            SubRequest
	 * @throws MOException
	 */
	public void prepare(SubRequest request) {
		System.err.println("prepare.......");
		OID cellOID = request.getVariableBinding().getOid();
		MOTableCellInfo cell = getCellInfo(cellOID);
		if (cell.getIndex() == null) {
			request.getStatus().setErrorStatus(PDU.inconsistentName);
			return;
		}
		if ((cell.getColumn() >= 0) && (cell.getColumn() < columns.length)) {
			MOColumn col = getColumn(cell.getColumn());
			if (logger.isDebugEnabled()) {
				logger.debug("Preparing sub-request ("
						+ request.getVariableBinding() + ")" + " for column: "
						+ col);
			}

			if ((col instanceof MOMutableColumn)
					&& (col.getAccess().isAccessibleForWrite())) {
				MOMutableColumn mcol = (MOMutableColumn) col;
				// check index
				// if (getIndexDef().isValidIndex(cell.getIndex())) { //Jacky
				// Fang comment
				MOTableRow row = model.getRow(cell.getIndex());
				if (row == null) {
					// look for already prepared row
					row = (MOTableRow) getNewRows(request.getRequest()).get(
							cell.getIndex());
				}
				if (row != null) {
					prepare(request, cell, mcol, row, false);
					request.completed();
					return;
				} else if (model instanceof MOMutableTableModel) {
					if (logger.isDebugEnabled()) {
						logger.debug("Trying to create new row '"+ cell.getIndex() + "'");
					}
					MOMutableTableModel mmodel = (MOMutableTableModel) model;
					// create new row
					try {
						row = createRow(request, cell, mmodel);
						if (row == null) {
							request.getStatus().setErrorStatus(PDU.noCreation);
						} else {
							prepare(request, cell, mcol, row, true);
							request.completed();
						}
					} catch (UnsupportedOperationException ex) {
						request.getStatus().setErrorStatus(PDU.noCreation);
					}
				} else {
					request.getStatus().setErrorStatus(PDU.noCreation);
				}
				// Jacky Fang comment
				// }
				// else {
				// // invalid index
				// if (logger.isDebugEnabled()) {
				// logger.debug("Invalid index '"+cell.getIndex()+
				// "' for row creation in table "+getID());
				// }
				// request.getStatus().setErrorStatus(PDU.noCreation);
				// }
			} else {
				// read-only column
				request.getStatus().setErrorStatus(PDU.notWritable);
			}
		} else {
			request.getStatus().setErrorStatus(PDU.noCreation);
		}
	}

	private MOTableRow createRow(SubRequest request, MOTableCellInfo cell,
			MOMutableTableModel mmodel) throws UnsupportedOperationException {
		MOColumn col = getColumn(cell.getColumn());
		if (!col.getAccess().isAccessibleForCreate()) {
			// creation not allowed
			return null;
		}
		Variable[] initialValues = new Variable[getColumnCount()];
		getChangesFromRequest(cell.getIndex(), null, request, initialValues,
				true, true);
		MOTableRow row = mmodel.createRow(cell.getIndex(), initialValues);
		getNewRows(request.getRequest()).put(row.getIndex(), row);
		return row;
	}

	private void prepare(SubRequest request, MOTableCellInfo cell, MOMutableColumn mcol, MOTableRow row, boolean creation) {
		System.err.println("prepare(SubRequest request, MOTableCellInfo cell, MOMutableColumn mcol, MOTableRow row, boolean creation)......");
		if (moChangeListeners != null) {
			MOChangeEvent changeEvent = new MOChangeEvent(this, new CellProxy(
					cell), cell.getCellOID(), (creation) ? null : row
					.getValue(cell.getColumn()), request.getVariableBinding()
					.getVariable(), true);
			fireBeforePrepareMOChange(changeEvent);
			if (changeEvent.getDenyReason() != PDU.noError) {
				request.getStatus().setErrorStatus(changeEvent.getDenyReason());
			}
		}
		ChangeSet changeSet = getPendingChangeSet(request, cell.getIndex());
		if (changeSet == null) {
			changeSet = addPendingChanges(request, row, creation);
		}
		if ((moTableRowListeners != null) && (!request.hasError())) {
			if (isChangeSetComplete(request, row.getIndex(), cell.getColumn())) {
				MOTableRowEvent rowEvent = new MOTableRowEvent(this, this, row,
						changeSet, (creation) ? MOTableRowEvent.CREATE
								: MOTableRowEvent.CHANGE, true);
				fireRowChanged(rowEvent);
				if (rowEvent.getVetoStatus() != PDU.noError) {
					if (rowEvent.getVetoColumn() >= 0) {
						int colID = columns[rowEvent.getVetoColumn()]
								.getColumnID();
						OID prefix = new OID(getOID());
						prefix.append(colID);
						SubRequest r = request.getRequest().find(prefix);
						if (r != null) {
							r.getStatus().setErrorStatus(
									rowEvent.getVetoStatus());
						} else {
							request.getRequest().setErrorStatus(
									rowEvent.getVetoStatus());
						}
					} else {
						request.getRequest().setErrorStatus(
								rowEvent.getVetoStatus());
					}
				}
			}
		}
		if (request.getStatus().getErrorStatus() == PDU.noError) {
			mcol.prepare(request, row, changeSet, cell.getColumn());
			MOChangeEvent changeEvent = new MOChangeEvent(this, new CellProxy(cell), cell.getCellOID(), row.getValue(cell.getColumn()),
					request.getVariableBinding().getVariable(), true);
			fireAfterPrepareMOChange(changeEvent);
			if (changeEvent.getDenyReason() != PDU.noError) {
				request.getStatus().setErrorStatus(changeEvent.getDenyReason());
			} else {
				if ((row instanceof MOMutableRow2PC)
						&& (isChangeSetComplete(request, row.getIndex(), cell
								.getColumn()))) {
					((MOMutableRow2PC) row).prepareRow(request, changeSet);
				}
			}
		}
	}

	protected int getChangesFromRequest(OID index, MOTableRow row,
			SubRequest request, Variable[] values, boolean setDefaultValues,
			boolean newRow) {
		int lastChangedColumn = -1;
		// assign default values
		if (setDefaultValues) {
			for (int i = 0; (i < values.length) && (i < getColumnCount()); i++) {
				if (columns[i] instanceof MOMutableColumn) {
					values[i] = ((MOMutableColumn) columns[i])
							.getDefaultValue();
				}
			}
		}
		Request req = request.getRequest();
		for (Iterator it = req.iterator(); it.hasNext();) {
			SubRequest sreq = (SubRequest) it.next();
			OID id = sreq.getVariableBinding().getOid();
			MOTableCellInfo cellInfo = getCellInfo(id);
			if (index.equals(cellInfo.getIndex())) {
				int col = cellInfo.getColumn();
				if ((col >= 0) && (col < values.length)) {
					Variable v = sreq.getVariableBinding().getVariable();
					// check that value is really changed
					if ((v != null)
							&& ((row == null) || (newRow)
									|| (row.size() <= col) || (!v.equals(row
									.getValue(col))))) {
						values[col] = v;
						lastChangedColumn = col;
					}
				}
			}
		}
		return lastChangedColumn;
	}

	protected boolean hasNewRows(Object key) {
		return ((newRows != null) && (newRows.get(key) != null));
	}

	protected Map getNewRows(Object key) {
		if (newRows == null) {
			newRows = new WeakHashMap(4);
		}
		Map rowMap = (Map) newRows.get(key);
		if (rowMap == null) {
			rowMap = new HashMap(5);
			newRows.put(key, rowMap);
		}
		return rowMap;
	}

	protected synchronized boolean isChangeSetComplete(SubRequest subRequest,
			OID index, int column) {
		ChangeSet changeSet = getPendingChangeSet(subRequest, index);
		if (changeSet != null) {
			return (changeSet.getLastChangedColumn() == column);
		}
		return true;
	}

	/*
	 * protected synchronized ChangeSet addPendingChange(SubRequest subRequest,
	 * OID index, int column, Variable value) { if (pendingChanges == null) {
	 * pendingChanges = new WeakHashMap(4); } Map rowMap = (Map)
	 * pendingChanges.get(subRequest.getRequest()); if (rowMap == null) { rowMap =
	 * new HashMap(5); pendingChanges.put(subRequest.getRequest(), rowMap); }
	 * ChangeSet changeSet = (ChangeSet) rowMap.get(index); if (changeSet ==
	 * null) { changeSet = new ChangeSet(index, new Variable[getColumnCount()]); }
	 * changeSet.setValue(column, value); return changeSet; }
	 */

	protected synchronized ChangeSet addPendingChanges(SubRequest subRequest,
			MOTableRow row, boolean newRow) {
		if (pendingChanges == null) {
			pendingChanges = new WeakHashMap(4);
		}
		Map rowMap = (Map) pendingChanges.get(subRequest.getRequest());
		if (rowMap == null) {
			rowMap = new HashMap(5);
			pendingChanges.put(subRequest.getRequest(), rowMap);
		}
		Variable[] values = new Variable[getColumnCount()];
		int lastChangedColumn = getChangesFromRequest(row.getIndex(), row,
				subRequest, values, newRow, newRow);
		ChangeSet changeSet = new ChangeSet(row.getIndex(), values);
		changeSet.lastChangedColumn = lastChangedColumn;
		rowMap.put(row.getIndex(), changeSet);
		return changeSet;
	}

	protected ChangeSet getPendingChangeSet(SubRequest subRequest, OID index) {
		if (pendingChanges != null) {
			Map rowMap = (Map) pendingChanges.get(subRequest.getRequest());
			if (rowMap != null) {
				return (ChangeSet) rowMap.get(index);
			}
		}
		return null;
	}

	public void cleanup(SubRequest request) {
		OID cellOID = request.getVariableBinding().getOid();
		MOTableCellInfo cell = getCellInfo(cellOID);
		if ((cell.getIndex() == null) || (cell.getColumn() < 0)) {
			return;
		}
		MOColumn col = getColumn(cell.getColumn());
		if (logger.isDebugEnabled()) {
			logger.debug("Cleaning-up sub-request ("
					+ request.getVariableBinding() + ") for column: " + col);
		}
		MOMutableTableRow row = (MOMutableTableRow) model.getRow(cell
				.getIndex());
		if ((row != null) && (col instanceof MOMutableColumn)) {
			((MOMutableColumn) col).cleanup(request, row, cell.getColumn());
		}
		if ((row instanceof MOMutableRow2PC)
				&& isChangeSetComplete(request, row.getIndex(), cell
						.getColumn())) {
			((MOMutableRow2PC) row).cleanupRow(request, getPendingChangeSet(
					request, row.getIndex()));
		}
		request.completed();
	}

	public void undo(SubRequest request) {
		OID cellOID = request.getVariableBinding().getOid();
		MOTableCellInfo cell = getCellInfo(cellOID);
		MOMutableColumn col = (MOMutableColumn) getColumn(cell.getColumn());
		if (logger.isDebugEnabled()) {
			logger.debug("Undoing sub-request (" + request.getVariableBinding()
					+ ") for column: " + col);
		}
		if (hasNewRows(request.getRequest())) {
			((MOMutableTableModel) model).removeRow(cell.getIndex());
		} else {
			MOMutableTableRow row = (MOMutableTableRow) model.getRow(cell
					.getIndex());
			if (row != null) {
				col.undo(request, row, cell.getColumn());
			}
			if ((row instanceof MOMutableRow2PC)
					&& isChangeSetComplete(request, row.getIndex(), cell
							.getColumn())) {
				((MOMutableRow2PC) row).undoRow(request, getPendingChangeSet(
						request, row.getIndex()));
			}
		}
	}

	public OID getOID() {
		return oid;
	}

	public void setModel(MOTableModel model) {
		this.model = model;
	}

	public void setVolatile(boolean isVolatile) {
		this.isVolatile = isVolatile;
	}

	public MOTableModel getModel() {
		return model;
	}

	public MOColumn[] getColumns() {
		return columns;
	}

	public MOTableIndex getIndexDef() {
		return indexDef;
	}

	public boolean isVolatile() {
		return isVolatile;
	}

	public OID getLowerBound() {
		return oid;
	}

	public OID getUpperBound() {
		OID upperBound = new OID(oid);
		int lastID = oid.size() - 1;
		/**
		 * This is not quite correct because we would have to search up the tree
		 * if the last sub ID is 0xFFFFFFFF, but since a table OID must end on 1
		 * by SMI rules we should be on the safe side here.
		 */
		upperBound.set(lastID, oid.get(lastID) + 1);
		return upperBound;
	}

	public boolean isLowerIncluded() {
		return false;
	}

	public boolean isUpperIncluded() {
		return false;
	}

	public boolean isCovered(MOScope other) {
		return DefaultMOScope.covers(this, other);
	}

	public boolean isOverlapping(MOScope other) {
		return DefaultMOScope.overlaps(this, other);
	}

	public synchronized void addMOChangeListener(MOChangeListener l) {
		if (moChangeListeners == null) {
			moChangeListeners = new Vector(2);
		}
		moChangeListeners.add(l);
	}

	public synchronized void removeMOChangeListener(MOChangeListener l) {
		if (moChangeListeners != null) {
			moChangeListeners.remove(l);
		}
	}

	protected void fireBeforePrepareMOChange(MOChangeEvent changeEvent) {
		if (moChangeListeners != null) {
			Vector listeners = moChangeListeners;
			int count = listeners.size();
			for (int i = 0; i < count; i++) {
				((MOChangeListener) listeners.elementAt(i))
						.beforePrepareMOChange(changeEvent);
			}
		}
	}

	protected void fireAfterPrepareMOChange(MOChangeEvent changeEvent) {
		if (moChangeListeners != null) {
			Vector listeners = moChangeListeners;
			int count = listeners.size();
			for (int i = 0; i < count; i++) {
				((MOChangeListener) listeners.elementAt(i))
						.afterPrepareMOChange(changeEvent);
			}
		}
	}

	protected void fireBeforeMOChange(MOChangeEvent changeEvent) {
		if (moChangeListeners != null) {
			Vector listeners = moChangeListeners;
			int count = listeners.size();
			for (int i = 0; i < count; i++) {
				((MOChangeListener) listeners.elementAt(i))
						.beforeMOChange(changeEvent);
			}
		}
	}

	protected void fireAfterMOChange(MOChangeEvent changeEvent) {
		if (moChangeListeners != null) {
			Vector listeners = moChangeListeners;
			int count = listeners.size();
			for (int i = 0; i < count; i++) {
				((MOChangeListener) listeners.elementAt(i))
						.afterMOChange(changeEvent);
			}
		}
	}

	public synchronized void addMOTableRowListener(MOTableRowListener l) {
		if (moTableRowListeners == null) {
			moTableRowListeners = new Vector(2);
		}
		moTableRowListeners.add(l);
	}

	public synchronized void removeMOTableRowListener(MOTableRowListener l) {
		if (moTableRowListeners != null) {
			moTableRowListeners.remove(l);
		}
	}

	protected void fireRowChanged(MOTableRowEvent event) {
		if (moTableRowListeners != null) {
			Vector listeners = moTableRowListeners;
			int count = listeners.size();
			for (int i = 0; i < count; i++) {
				((MOTableRowListener) listeners.elementAt(i)).rowChanged(event);
			}
		}
	}

	public static class ChangeSet implements MOTableRow {

		private OID index;
		private Variable[] values;
		private int lastChangedColumn = -1;

		public ChangeSet(OID index, Variable[] values) {
			this.index = index;
			this.values = values;
		}

		public OID getIndex() {
			return index;
		}

		public int getLastChangedColumn() {
			return lastChangedColumn;
		}

		public void setValue(int column, Variable value) {
			System.err.println("setValue(int column, Variable value).........");
			values[column] = value;
			this.lastChangedColumn = column;
		}

		public Variable getValue(int column) {
			System.err.println("getValue(int column).........");
			return values[column];
		}

		public MOTableRow getBaseRow() {
			return null;
		}

		public int size() {
			return values.length;
		}

		public void setBaseRow(MOTableRow baseRow) {
			throw new UnsupportedOperationException();
		}
	}

	class CellProxy implements ManagedObject {

		private MOTableCellInfo cellInfo;
		private MOScope scope;

		public CellProxy(MOTableCellInfo cellInfo) {
			this.cellInfo = cellInfo;
			this.scope = new OIDScope(cellInfo.getCellOID());
		}

		public MOScope getScope() {
			return scope;
		}

		public OID find(MOScope range) {
			if (range.isCovered(scope)) {
				return cellInfo.getCellOID();
			}
			return null;
		}

		public void get(SubRequest request) {
			NMSMOTable.this.get(request);
		}

		public boolean next(SubRequest request) {
			return NMSMOTable.this.next(request);
		}

		public void prepare(SubRequest request) {
			NMSMOTable.this.prepare(request);
		}

		public void commit(SubRequest request) {
			NMSMOTable.this.commit(request);
		}

		public void undo(SubRequest request) {
			NMSMOTable.this.undo(request);
		}

		public void cleanup(SubRequest request) {
			NMSMOTable.this.cleanup(request);
		}

		public MOTable getTable() {
			return NMSMOTable.this;
		}
	}

	class CellInfo implements MOTableCellInfo {

		private OID index;
		private int id = 0;
		private int col = -1;
		private MOTableRow row;

		public CellInfo(OID oid) {
			///index null
			this.index = getIndexPart(oid);
			if ((oid.size() > NMSMOTable.this.oid.size())
					&& (oid.startsWith(NMSMOTable.this.oid))) {
				id = oid.get(NMSMOTable.this.oid.size());
			}
			/*
			 * else { id = columns[0].getColumnID(); }
			 */
		}

		public CellInfo(OID index, int column, int columnID) {
			this.index = index;
			this.col = column;
			this.id = columnID;
		}

		public CellInfo(OID index, int column, int columnID, MOTableRow row) {
			this(index, column, columnID);
			this.row = row;
		}

		public OID getIndex() {
			return index;
		}

		public int getColumn() {
			if (col < 0) {
				col = getColumnIndex(id);
			}
			return col;
		}

		public int getColumnID() {
			return id;
		}

		public OID getCellOID() {
			return NMSMOTable.this.getCellOID(index, col);
		}

		public MOTableRow getRow() {
			return row;
		}
	}

	public OID getID() {
		return getLowerBound();
	}

	public void load(MOInput input) throws IOException {
		if (input.getImportMode() == ImportModes.REPLACE_CREATE) {
			int count = removeAll();
			if (logger.isDebugEnabled()) {
				logger.debug("Removed " + count + " rows from " + getID()
						+ " because importing with a REPLACE import mode");
			}
		}
		Sequence seq = input.readSequence();
		for (int i = 0; i < seq.getSize(); i++) {
			IndexedVariables rowValues = input.readIndexedVariables();
			boolean rowExists = model.containsRow(rowValues.getIndex());
			if ((input.getImportMode() == ImportModes.CREATE) && (rowExists)) {
				logger.debug("Row '" + rowValues.getIndex()
						+ "' not imported because it already exists in table "
						+ getID() + " and import mode is CREATE");
				continue;
			}
			if (rowExists) {
				removeRow(rowValues.getIndex());
			}
			/** @todo Do real update here instead of delete/create */
			if ((rowExists)
					|| ((input.getImportMode() == ImportModes.CREATE)
							|| (input.getImportMode() == ImportModes.REPLACE_CREATE) || (input
							.getImportMode() == ImportModes.UPDATE_CREATE))) {
				MOTableRow row = null;
				try {
					row = createRow(rowValues.getIndex(), rowValues.getValues());
				} catch (UnsupportedOperationException uoex) {
					logger.debug("Could not create row by row factory: "
							+ uoex.getMessage());
					// ignore
				}
				if (row == null) {
					row = new DefaultMOTableRow(rowValues.getIndex(), rowValues
							.getValues());
					fireRowChanged(new MOTableRowEvent(this, this, row,
							MOTableRowEvent.CREATE));
				}
				addRow(row);
			}
		}
	}

	public void save(MOOutput output) throws IOException {
		System.err.println("save(MOOutput output)...");
		List rowsToSave = new LinkedList();
		synchronized (model) {
			for (Iterator it = model.iterator(); it.hasNext();) {
				MOTableRow row = (MOTableRow) it.next();
				boolean volatileRow = false;
				for (int i = 0; i < columns.length; i++) {
					if (columns[i].isVolatile(row, i)) {
						volatileRow = true;
						break;
					}
				}
				if (!volatileRow) {
					Variable[] values = getPersistentValues(row);
					IndexedVariables rowValues = new IndexedVariables(row
							.getIndex(), values);
					rowsToSave.add(rowValues);
				}
			}
		}
		Sequence group = new Sequence(rowsToSave.size());
		output.writeSequence(group);
		for (Iterator it = rowsToSave.iterator(); it.hasNext();) {
			IndexedVariables rowValues = (IndexedVariables) it.next();
			output.writeIndexedVariables(rowValues);
		}
	}

	/**
	 * Gets the values of a row that need to be made persistent on behalf of a
	 * {@link #save(MOOutput output)} call.
	 * 
	 * @param row
	 *            a MOTableRow that is being saved into a MOOutput stream.
	 * @return an array of <code>Variable</code> instances that need to be
	 *         saved. Typically, these are all columns of the row - including
	 *         hidden extension columns/values.
	 * @since 1.2
	 */
	protected Variable[] getPersistentValues(MOTableRow row) {
		Variable[] values = new Variable[row.size()];
		for (int i = 0; i < values.length; i++) {
			values[i] = row.getValue(i);
		}
		return values;
	}

	public Variable[] getDefaultValues() {
		Variable[] values = new Variable[getColumnCount()];
		for (int i = 0; (i < values.length); i++) {
			if (columns[i] instanceof MOMutableColumn) {
				values[i] = ((MOMutableColumn) columns[i]).getDefaultValue();
			}
		}
		return values;
	}

	public String toString() {
		return "NMSMOTable[id=" + getID() + ",index=" + getIndexDef()
				+ ",columns=" + Arrays.asList(getColumns()) + "]";
	}

	public boolean covers(OID oid) {
		return isCovered(new DefaultMOScope(oid, true, oid, true));
	}

	private static class RowCacheEntry {
		private MOTableRow row;
		private OID searchLowerBound;
		private boolean searchLowerBoundIncluded;

		RowCacheEntry(MOTableRow row, OID searchLowerBound,
				boolean searchLowerBoundIncluded) {
			this.row = row;
			this.searchLowerBound = searchLowerBound;
			this.searchLowerBoundIncluded = searchLowerBoundIncluded;
		}
	}

	public static AgentTableService getAgentTableService() {
		return agentTableService;
	}

	public void setAgentTableService(AgentTableService agentTableService) {
		this.agentTableService = agentTableService;
	}
}
